﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace Gaming
{
    public static class HandType
    {
        public static int RoyalFlush = 10;
        public static int StraightFlush = 9;
        public static int Care = 8;
        public static int FullHouse = 7;
        public static int Flush = 6;
        public static int Straight = 5;
        public static int Three = 4;
        public static int TwoPair = 3;
        public static int Pair = 2;
        public static int Kicker = 1;
    }

    public class Hand : IComparable<Hand>
    {
        #region Fields
        private int _code;
        #endregion

        private static bool _debug;

        #region Properties

        /// <summary>
        /// CombinationType
        /// </summary>
        public int Type
        {
            get { return GetMask(20); }
            set { SetMask(value, 20); }
        }

        /// <summary>
        /// Internal combination code.
        /// Combination code is a list of 4bit groups. See bit layout:
        /// tttt 1111 2222 3333 4444 5555
        /// here:
        ///   tttt - 4 bits for type of combination
        ///   1111,2222,etc - bits for ranks of cards-participants (ordered by rank of cards, highest first)
        /// </summary>
        public int Code { get { return _code; } }
        
        
        /// <summary>
        /// List of ranks of 5 cards in the combination
        /// </summary>
        

        /// <summary>
        /// Rank list of cards in the combination
        /// </summary>
        //public RankList Ranks { get { return new RankList(this); } }

        public int GetRank(int i)
        {
            return GetMask(16 - 4 * i);
        }

        #endregion

        #region Constructors
        public Hand(int type, IEnumerable<Card> cards)
            //
        {
            _code = 0;
            SetMask(type, 20);

            int i = 0;
            foreach (Card card in cards)
            {
                SetMask(card.Rank, 16 - 4 * i);
                i++;
                if (i >= 5)
                    break;
            }
        }

        public Hand() { }

        public Hand(int code)
        {
            _code = code;
        }
        #endregion

        #region Internal
        internal void SetMask(int value, int offset)
        {
            _code = _code | ((value & 0xF) << offset);
        }

        internal int GetMask(int offset)
        {
            return (_code >> offset) & 0xF;
        }
        #endregion

        /// <summary>
        /// Construct combination from CombinationType
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Hand FromType(int type)
        {
            Hand h = new Hand();
            h.Type = type;
            return h;
        }

        /// <summary>
        /// Construct combination from CombinationType
        /// </summary>
        /// <param name="type">CombinationType</param>
        /// <param name="set">Card Set</param>
        /// <returns></returns>
        public static Hand FromTypeAndCards(int type, CardSet set)
        {
            return new Hand(type, set);
        }

        /// <summary>
        /// Parse combination of 5 cards
        /// </summary>
        /// <param name="set">Cardset with exactly 5 cards</param>
        /// <returns>Combination parsed</returns>
        public static Hand From5Cards(CardSet set)
        {
            if (set.Count != 5)
                throw new InvalidOperationException();

            // check straight
            bool straight = true;
            bool flush = true;
            int[] sameCount = new int[] { 0, 0, 0, 0, 0 };
            int counter = 1;
            bool aceAsOne = false;
            for (int i = 0; i < 5 - 1; i++)
            {
                if (set[i].Rank - set[i + 1].Rank != 1)
                    if (!(set[0].Rank == (int)Ranks.Ace && set[1].Rank == (int)Ranks.Five))
                        straight = false;
                    else
                        aceAsOne = true;
                if (set[i].Rank == set[i + 1].Rank)
                    counter++;
                else
                {
                    counter = 1;
                }
                
                sameCount[counter]++;

                if (set[i].Suit != set[i + 1].Suit)
                    flush = false;
            }

            if (straight && flush)
            {
                if (set[0].Rank == (int)Gaming.Ranks.Ace)
                    return Hand.FromTypeAndCards(HandType.RoyalFlush, set);
                return Hand.FromTypeAndCards(HandType.StraightFlush, set);
            }

            if (straight)
            {
                if (aceAsOne)
                {
                    CardSet other = new CardSet(5);
                    for (int i = 0; i < 4; i++)
                        other.cards[i] = set.cards[i + 1];
                    other.cards[4] = Card.FromSuitRank(0, 1);
                    return Hand.FromTypeAndCards(HandType.Straight, other);
                }
                return Hand.FromTypeAndCards(HandType.Straight, set);
            }

            if (flush)
                return Hand.FromTypeAndCards(HandType.Flush, set);

            if(_debug)
                for (int i = 2; i <= 4; i++)
                    Console.WriteLine("[{0}]=[{1}]", i, sameCount[i]);

            if (sameCount[4] == 1)
                return Hand.FromTypeAndCards(HandType.Care, set);

            if (sameCount[3] == 1 && sameCount[2] == 1)
                return Hand.FromTypeAndCards(HandType.Three, set);

            if (sameCount[3] == 1 && sameCount[2] == 2)
                return Hand.FromTypeAndCards(HandType.FullHouse, set);

            if (sameCount[2] == 2)
                return Hand.FromTypeAndCards(HandType.TwoPair, set);

            if (sameCount[2] == 1)
                return Hand.FromTypeAndCards(HandType.Pair, set);

            return Hand.FromTypeAndCards(HandType.Kicker, set);
        }

        public static Hand FromCode(int code)
        {
            return new Hand(code);
        }

        public override string ToString()
        {
            string r =  string.Format("{0} [{1}] @ ",_code, _combNames[Type]);
            for(int i=0;i<5;i++)
                r=r+Card.RankChar(this.GetRank(i));
            return r;
        }

        public static string CombName(int type) { return _combNames[type]; }

        private static string[] _combNames = new string[]{
            "",
            "Kicker",
            "Pair",
            "TwoPair",
            "Three",
            "Straight", 
            "Flush",
            "FullHouse",
            "Care", 
            "StraightFlush", 
            "RoyalFlush"
        };
        /// <summary>
        /// Parse combination from 5 or more cards.
        /// 
        /// To complete, enumerates all possible combinations of 5 cards from given larger cardset
        /// </summary>
        /// <param name="set">Cardset with 5+ cards</param>
        /// <returns>Combination parsed</returns>
        
        private static int bestHandValue=0;
        
        public static Hand FromCards(CardSet set)
        {
            CardGroup five = new CardGroup(5);
            five.Allowed = new CardSetList(set);
            CardEnumerator enu = new CardEnumerator(five);
            bestHandValue=0;
            enu.EnumerationDone = delegate(CardGroup g, CardEnumerator e)
            {
                int value = Hand.From5Cards(g).Code;
                if (value > bestHandValue)
                    bestHandValue = value;
            };
            enu.EnumerateCombinations();
            return Hand.FromCode(bestHandValue);

        }

        #region IComparable<Combination> Members

        public int CompareTo(Hand other)
        {
            return other.Code - Code;
        }

        #endregion
    }
}